import socket
import threading
import time
from collections import defaultdict
tuplespace = defaultdict(str)
lock = threading.Lock()
totalclients = 0
operations = 0
reads = 0
gets = 0
puts = 0
errors = 0

def print_summary_info():
    global tuplespace, totalclients, operations, reads, gets, puts, errors
    while True:
        time.sleep(10)
        with lock:
            num_tuples = len(tuplespace)
            total_tuple_size = sum(len(key) + len(value) for key, value in tuplespace.items())
            avg_tuple_size = total_tuple_size / num_tuples if num_tuples > 0 else 0
            total_key_size = sum(len(key) for key in tuplespace.keys())
            avg_key_size = total_key_size / num_tuples if num_tuples > 0 else 0
            total_value_size = sum(len(value) for value in tuplespace.values())
            avg_value_size = total_value_size / num_tuples if num_tuples > 0 else 0

            print("Tuple Space Summary:")
            print(f"  Number of tuples: {num_tuples}")
            print(f"  Average tuple size: {avg_tuple_size}")
            print(f"  Average key size: {avg_key_size}")
            print(f"  Average value size: {avg_value_size}")
            print(f"  Total clients connected: {totalclients}")
            print(f"  Total operations: {operations}")
            print(f"  Total READs: {reads}")
            print(f"  Total GETs: {gets}")
            print(f"  Total PUTs: {puts}")
            print(f"  Total errors: {errors}")


def setup_server_socket(port):
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind(('0.0.0.0', port))
    server_socket.listen(10)
    print(f"Server is listening on port {port}...")
    return server_socket


def handle_client_connection(client_socket, client_address):
    global totalclients
    totalclients += 1
    print(f"New client connection: {client_address}")
    return client_socket, client_address


def handle_client_request(client_socket, client_address):
    global operations, reads, gets, puts, errors
    try:
        data = client_socket.recv(1024).decode('utf-8')
        if not data:
            print(f"Client {client_address} disconnected")
            return

        size = int(data[:3])
        command = data[3]
        key = data[4:size].strip()
        value = data[size:].strip() if command == 'P' else ''

        operations += 1
        response = ""

        with lock:
            if command == 'R':
                reads += 1
                if key in tuplespace:
                    response = f"OK ({key}, {tuplespace[key]}) read"
                else:
                    response = f"ERR {key} does not exist"
            elif command == 'G':
                gets += 1
                if key in tuplespace:
                    response = f"OK ({key}, {tuplespace.pop(key)}) removed"
                else:
                    response = f"ERR {key} does not exist"
            elif command == 'P':
                puts += 1
                if key in tuplespace:
                    response = f"ERR {key} already exists"
                else:
                    tuplespace[key] = value
                    response = f"OK ({key}, {value}) added"

        client_socket.send(response.encode('utf-8'))
    except Exception as e:
        errors += 1
        print(f"Error occurred while handling client {client_address}: {e}")


def close_client_connection(client_socket):
    client_socket.close()

def start_server(port):
    server_socket = setup_server_socket(port)

    summary_thread = threading.Thread(target=print_summary_info)
    summary_thread.daemon = True
    summary_thread.start()

    while True:
        client_socket, client_address = server_socket.accept()
        client_socket, client_address = handle_client_connection(client_socket, client_address)
        handle_client_request(client_socket, client_address)
        close_client_connection(client_socket)


if __name__ == "__main__":
    SERVER_PORT = 51234
    start_server(SERVER_PORT)